深入理解MoveConstructor和MoveAssignment

之前的一篇博客讲了左值引用和右值引用,并且细致地说到了右值实际上就是“一闪而过”的结果,它是程序在计算时候的一个中间量,这个中间量如果我们能够有效的利用上,那么我们再创建对象(Constructor)的时候或是给已有对象(Assignment)赋值的时候很多资源就不用再重新申请了,直接把右值的资源“steal”过来。不要小看这一步的改变,如果你的程序频繁地创建对象,并且一个对象创建需要进行申请很多内存资源和计算的话,那么这一小步就能够在效率上提高很多。基于以上的思想,C++11中引入Move Constructor和Move Assignment。下面我们写一段程序,理解一下这两个新伙伴。

class Object{
public:
    Object (string* resource) : _resource(resource){
    }
    Object (Object& object) : _resource(new string(*object._resource)){
    }
    Object (Object&& object) : _resource(object._resource){
        object._resource = nullptr;
    }
    Object& operator=(Object& object){
        if (&object != this){
            _resource = new string(*object._resource);
        }
        return *this;
    }
    Object& operator=(Object&& object){
        if (&object != this){
            if (_resource != nullptr){
                delete _resource;
            }
            _resource = object._resource;
            object._resource = nullptr;
        }
        return *this;
    }
    ~Object(){
        if (_resource != nullptr){
            delete _resource;
        }
    }
private:
    string* _resource = nullptr;
};

可以看到在Object类中与以往相比有两个新成员函数:Object (Object&& object)和Object& opreator=(Object&& object),他们就是Move Constructor和Move Assignment。可以看到他们的特点是输入参数是右值引用,他们“steal”输入参数中的资源,节省资源提高效率,充分利用右值引用的特点,其实很好理解。

但是如果想要深入理解Move Constructor和Move Assignment,需要注意的还有很多。

1.众所周知,对于一个类,如果我们不定义它的Constructor、Copy Constructor、Copy Assignment和Destructor,编译器能够帮我们合成这些必备的函数。但是对于Move Constructor和Move Assignment,如果我们不定义他们,编译器也不会定义他们。但是有一个特例,就是如果一个类中如果他的成员变量都是可以“move”的(成员变量类自身定义了Move Constructor和Move Assignment),那么编译器会自动为这个类生成Move Constructor和Move Assignment。

struct X{
    int i;    // build-in类型是可以“move”的
    string s; // string是可以“move”的
}; // 所以编译器能够为X合成Move Constructor和Move Assignment
struct Y{
    X x;     // X是可以“move”的
}; // 所以编译器能够为Y合成move Constructor和Move Assignment

我们对于想要编译器合成的部分,可以使用using = default,但是对于Move Constructor和Move Assignment,仍然需要满足一定条件,否则即使使用using = default,最后的结果也不能够合成(using = delete)。a.在类中有成员变量不能够“move”的类;b.对于没有析构函数(desrucor = delete)的类(这种类能够被new出来,但是不能够被delete);c.有const或是引用的成员变量的类。

2.对于一个没有Move Constructor和Move Assignment的类,当传入的参数是右值引用,那么右值引用会转换成const左值引用,然后调用该类的Copy Constructor和Copy Assignment。

3.Copy-and-Swap Assignment Operator。这是一个有些trick的方式去定义Assignment Operator。还是上面的例子,我们可以这样定义Assignment Operator:

Object& operator=(Object object){

swap(*this, object);

return *this;

}

和以往不同的是这里的输入参数不是引用,而是一个实例,这也正是这么定义Assignment trick的地方。接着往下看:

object2 = object1 //object1是左值,所以Copy Constructor将会被调用

object2 = std::move(object1) //Move Constructor将会被调用

这下子一目了然了吧,一个Copy-and-Swap Assignment Operator可以替代Copy Assignment和Move Assignment。

4.因为对于Copy Constructor和Copy Assignment涉及到内存的重新分配,所以在在Copy Constructor和Copy Assignment中不要抛出异常,因为一旦抛出异常,内存的分配状况不确定,从而出现不确定行为。

本页共60段,3390个字符,5176 Byte(字节)